Glide的自定义使用

您所在的位置:网站首页 glide 圆角 Glide的自定义使用

Glide的自定义使用

2024-01-14 20:32| 来源: 网络整理| 查看: 265

文章目录 前言一、自定义Glide功能模块的原理二、自定义Glide功能配置1.自定义glide功能模块的准备2.更改glide配置1)更改硬盘缓存策略的存储位置2)更改硬盘缓存存储容量3)更改图片的格式 3.替换glide组件 三、自定义Glide获取缓存图片的key1.构建唯一key2.使用唯一key获取缓存图片1)显示缓存图片2)获取缓存图片的文件

前言

Glide的用法是非常非常简单的,大多数情况下,我们想要实现的图片加载效果只需要一行代码就能解决了。但是Glide过于简洁的API也造成了一个问题,就是如果我们想要更改Glide的某些默认配置项,或是替换glide使用的组件应该怎么操作呢?

Glide的用法是非常非常简单,基本使用如下:

Glide.with(this).load(url).into(imageView);

1、with 调用Glide.with()方法用于创建一个加载图片的实例。with()方法可以接收Context、Activity或者Fragment类型的参数。with()方法中传入的实例会决定Glide加载图片的生命周期,如果传入的是Activity或者Fragment的实例,那么当这个Activity或Fragment被销毁的时候,图片加载也会停止。如果传入的是ApplicationContext,那么只有当应用程序被杀掉的时候,图片加载才会停止。 2、load 这个方法用于指定待加载的图片资源。Glide支持加载各种各样的图片资源,包括网络图片、本地图片、应用资源、二进制流、Uri对象等等。 3、into 让图片显示在哪个ImageView上,把这个ImageView的实例传进去。into()方法不仅仅是只能接收ImageView类型的参数,还支持很多更丰富的用法,不过那个属于高级技巧

以上代码就可以加载出图片,但是如果我们想要更改Glide的某些默认配置项,就似乎无从下手了,我们该怎么做呢?这个时候就需要用到glide的自定义模块功能了。自定义模块功能可以将更改Glide配置,替换Glide组件等操作独立出来,这样我们对Glide配置进行更改的情况下,且不会影响到Glide的图片的加载逻辑。

一、自定义Glide功能模块的原理

首先为何可以进行自定义配置?这里的自定义有两种,一种是更改glide的配置,另一种是替换glide的组件。

我们先来看下glide实例创建的过程中做了什么?glide调用with()方法,除了绑定生命周期,还有一些初始化的操作。首先glide使用了一个单例模式来获取Glide对象的实例,

/** * Get the singleton. * @return the singleton */ public static Glide get(Context context) { if (glide == null) { synchronized (Glide.class) { if (glide == null) { checkAndInitializeGlide(context); } } } return glide; }

然后 checkAndInitializeGlide方法 里调用了 initializeGlide 方法,

private static void initializeGlide(Context context) { Context applicationContext = context.getApplicationContext(); /** 首先获取GeneratedAppGlideModule的实例,是glide内置的配置 Defines a set of dependencies and options to use when initializing Glide within an application. AppGlideModule就是Glide的全局配置文件,定义一组在应用程序中初始化 Glide 时使用的依赖项和选项。 **/ GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules(); List manifestModules = Collections.emptyList(); /** 当annotationGeneratedModule =null || annotationGeneratedModule.isManifestParsingEnabled()时,isManifestParsingEnabled判断是否还支持Glide在AndroidMenifest.xml中进行指定的方式,默认是返回true。 就会去调用ManifestParser的parse()方法去解析AndroidManifest.xml文件中的配置, Classes that extend AppGlideModule must be annotated with com.bumptech.glide.annotation.GlideModule to be processed correctly. 扩展 AppGlideModule 的类必须使用 com.bumptech.glide.annotation.GlideModule 才能正确处理。 **/ if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) { /** 调用ManifestParser的parse()方法去解析AndroidManifest.xml文件中的配置 实际上就是将AndroidManifest中所有值为GlideModule的meta-data配置读取出来,并将自定义的GlideModule的实例保存到list。 由于可以自定义任意多个模块,因此这里我们将会得到一个GlideModule的List集合。 **/ manifestModules = new ManifestParser(applicationContext).parse(); } /** 从manifest中解析到我们自定义的GlideModule类,如果判断与注解生成的类重复,那么就可以去掉。 **/ if (annotationGeneratedModule != null && !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) { Set excludedModuleClasses = annotationGeneratedModule.getExcludedModuleClasses(); for (Iterator iterator = manifestModules.iterator(); iterator.hasNext();) { GlideModule current = iterator.next(); if (!excludedModuleClasses.contains(current.getClass())) { continue; } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "AppGlideModule excludes manifest GlideModule: " + current); } iterator.remove(); } } if (Log.isLoggable(TAG, Log.DEBUG)) { for (GlideModule glideModule : manifestModules) { Log.d(TAG, "Discovered GlideModule from manifest: " + glideModule.getClass()); } } RequestManagerRetriever.RequestManagerFactory factory = annotationGeneratedModule != null ? annotationGeneratedModule.getRequestManagerFactory() : null; //Glide的对象的建造者,GlideBuilder用于构建Glide单例 GlideBuilder builder = new GlideBuilder() .setRequestManagerFactory(factory); /** 接下来若manifestModules获取到自定义的配置,就会通过循环调用了每一个GlideModule的applyOptions和registerComponents方法,这时用户配置的GlideModule就会被调用,同时用户设置的参数也就被配置到Glide中。 **/ for (GlideModule module : manifestModules) { module.applyOptions(applicationContext, builder); } if (annotationGeneratedModule != null) { annotationGeneratedModule.applyOptions(applicationContext, builder); } //最后调用Glide的build()方法,并返回了一个Glide对象。也就是说,Glide对象的实例就是在这里创建的了 Glide glide = builder.build(applicationContext); for (GlideModule module : manifestModules) { module.registerComponents(applicationContext, glide, glide.registry); } if (annotationGeneratedModule != null) { annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry); } //最后一行创建一个Glide对象,然后将前面build创建的这些实例一起传入到Glide对象当中 Glide.glide = glide; }

GlideBuilder的build()方法:这个方法中会创建BitmapPool、MemoryCache、DiskCache、DecodeFormat等对象的实例,建立了资源请求线程池,本地缓存加载线程池,动画线程池,内存缓存器,磁盘缓存工具等等,接着构造了Engine数据加载引擎,最后再将Engine注入Glide,构建Glide。

public Glide build(Context context) { if (sourceExecutor == null) { sourceExecutor = GlideExecutor.newSourceExecutor(); } if (diskCacheExecutor == null) { diskCacheExecutor = GlideExecutor.newDiskCacheExecutor(); } if (memorySizeCalculator == null) { memorySizeCalculator = new MemorySizeCalculator.Builder(context).build(); } if (connectivityMonitorFactory == null) { connectivityMonitorFactory = new DefaultConnectivityMonitorFactory(); } if (bitmapPool == null) { int size = memorySizeCalculator.getBitmapPoolSize(); bitmapPool = new LruBitmapPool(size); } if (arrayPool == null) { arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes()); } if (memoryCache == null) { memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize()); } //硬盘缓存默认是InternalCacheDiskCacheFactory if (diskCacheFactory == null) { diskCacheFactory = new InternalCacheDiskCacheFactory(context); } if (engine == null) { engine = new Engine(memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor, GlideExecutor.newUnlimitedSourceExecutor()); } RequestManagerRetriever requestManagerRetriever = new RequestManagerRetriever( requestManagerFactory); return new Glide( context, engine, memoryCache, bitmapPool, arrayPool, requestManagerRetriever, connectivityMonitorFactory, logLevel, defaultRequestOptions.lock()); }

build()方法中创建任何对象的时候都做了一个空检查,只有在对象为空的时候才会去创建它的实例。也就是说,如果我们刚刚在applyOptions()方法中提前就给这些对象初始化并赋值,那么在这里的build()方法中就不会再去重新创建它们的实例了,从而也就达到了我们自定义更改Glide配置的功能的目的。

现在继续回到Glide的 initializeGlide() 方法中,得到了Glide对象的实例之后,接下来又通过一个循环调用了每一个GlideModule的registerComponents()方法,在这里我们可以加入替换Glide的组件的逻辑。

二、自定义Glide功能配置 1.自定义glide功能模块的准备

通过以上的分析,我们就可以实现自定义glide的功能模块类名为MyGlideModule,它需要实现GlideModule接口,自定义代码如下所示:

class MyGlideModule : GlideModule { override fun applyOptions(context: Context?, builder: GlideBuilder?){ } override fun registerComponents(context: Context?, glide: Glide?, registry: Registry?) { } }

在MyGlideModule类当中,我们重写了applyOptions()和registerComponents()方法,这两个方法分别就是用来更改Glide和配置以及替换Glide组件的。我们待会儿只需要在这两个方法中加入具体的逻辑,就能实现更改Glide配置或者替换Glide组件的功能了。 目前Glide还无法识别我们自定义的MyGlideModule,如果想要让它生效,还得在AndroidManifest.xml文件当中加入如下配置才行:

// 1)修改缓存文件存储位置 // 2) 修改存储容量 // 3) 修改图片格式 builder?.setDiskCache(ExternalCacheDiskCacheFactory(context,DISK_CACHE_SIZE)); } override fun registerComponents(context: Context?, glide: Glide?, registry: Registry?) { } } 2)更改硬盘缓存存储容量

另外,InternalCacheDiskCacheFactory和ExternalCacheDiskCacheFactory的默认硬盘缓存大小都是250M。也就是说,如果你的应用缓存的图片总大小超出了250M,那么Glide就会按照DiskLruCache算法的原则来清理缓存的图片。我们是可以对这个默认的缓存大小进行修改,其中journal文件是DiskLruCache算法的日志文件,这个文件必不可少,且只会有一个。

通过以上的更改,我们就自定义glide的缓存路径了,打开查看到手机文件目录下有glide缓存的图片,这些图片文件打不开,将其后缀修改为.png后就可以查看了, 在这里插入图片描述 在这里插入图片描述 我们打开journal文件可以看到: 在这里插入图片描述 简单看下这个文件, 文件开始的四行是文件的头。包含常量字符串"libcore.io.DiskLruCache",磁盘缓存的版本,应用的版本,存储大小。 后续每一个子行记录了每一个缓存的状态: DIRTY:意味着缓存被创建或被更新。每一个成功写入DIRTY状态行后续一定跟着一个CLEAN或REMOVE状态,代表这条缓存可读或已被删除。DIRTY状态后续没有CLEAN或REMOVE状态意味着临时文件需要被删除。 CLEAN:意味着一个缓存已经被成功发布并且可以从缓存里面访问了。一个发布状态的行后续跟着当前缓存的大小,以空格分割,如果一个key对应多个value,就有多个数值了。 READ:意味着最近被访问了 REMOVE:意味着当前缓存被删除了

journal文件是在缓存操作发生时追加的日志内容。

3)更改图片的格式

Glide加载图片的默认格式是RGB_565,

builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888); 3.替换glide组件

替换Glide组件功能需要在自定义模块的 registerComponents()方法中加入具体的替换逻辑。相比于更改Glide配置,替换Glide组件这个功能的难度就明显大了不少。这里就简单说明下是如何替换的。

首先来看一下Glide中目前有哪些组件吧,在Glide类的构造方法当中,如下所示: 在这里插入图片描述 可以看到,这里都是以调用register()方法的方式来注册一个组件,register()方法中传入的参数表示Glide支持使用哪种参数类型来加载图片,以及如何去处理这种类型的图片加载。

下面这行代码就是glide处理网络图片加载的组件,HttpUrlGlideUrlLoader.Factory则是要负责处理具体的网络通讯逻辑。 默认情况下,Glide使用的是基于原生HttpURLConnection进行订制的HTTP通讯组件,但是现在大多数的Android开发者都更喜欢使用OkHttp,因此将Glide中的HTTP通讯组件修改成OkHttp的这个需求比较常见。

register(GlideUrl.class,InputStream.class,newHttpUrlGlideUrlLoader.Factory());

Glide的网络通讯逻辑是由HttpUrlGlideUrlLoader.Factory来负责的吗,通过它的源码可知:HttpUrlGlideUrlLoader.Factory是一个内部类,外层的HttpUrlGlideUrlLoader类实现了ModelLoader这个接口,并重写了getResourceFetcher()方法。而在getResourceFetcher()方法中,又创建了一个HttpUrlFetcher的实例,在这里才是真正处理具体网络通讯逻辑的地方。 假如我们需要将Glide的HTTP通讯组件替换成OkHttp的话,就可以仿照HttpUrlGlideUrlLoader和HttpUrlFetcher去创建OkHttpGlideUrlLoader和OkHttpFetcher,并将其注册到Glide当中,将原来的HTTP通讯组件给替换掉就可以了,在我们自定义的MyGlideMoudle的registerComponents方法中添加如下代码逻辑,就可以实现glide 组件的替换

public class MyGlideModule implements GlideModule { ... @Override public void registerComponents(Context context, Glide glide) { glide.register(GlideUrl.class,InputStream.class,newOkHttpGlideUrlLoader.Factory()); } }

Glide官方给我们提供了非常简便的HTTP组件替换方式。并且除了支持OkHttp3之外,还支持OkHttp2和Volley。 我们只需要在gradle当中添加几行库的配置就行了。比如使用OkHttp3来作为HTTP通讯组件的配置如下:

dependencies { compile 'com.squareup.okhttp3:okhttp:3.9.0' compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0@aar' }

使用OkHttp2来作为HTTP通讯组件的配置如下:

dependencies { compile 'com.github.bumptech.glide:okhttp-integration:1.5.0@aar' compile 'com.squareup.okhttp:okhttp:2.7.5' }

使用Volley来作为HTTP通讯组件的配置如下:

dependencies { compile 'com.github.bumptech.glide:volley-integration:1.5.0@aar' compile 'com.mcxiaoke.volley:library:1.0.19' }

这些库背后的工作原理和自己手动实现替换HTTP组件的原理是一模一样的。但是学会了手动替换组件的原理我们就能更加轻松地扩展更多丰富的功能。

三、自定义Glide获取缓存图片的key

我们后台获取的图片的url会变,那我们如何去获取缓存在glide中的图片呢?但是原生Glide是没有提供任何Api用来获取glide缓存的图片,怎么办呢?

1.构建唯一key

我们在load()方法中传入的图片url地址的时候,发现Glide在内部会把这个url地址包装成一个GlideUrl对象。GlideUrl类的构造函数接收两种类型的参数,一种是url字符串,一种是URL对象。然后getCacheKey()方法中的判断逻辑非常简单,如果传入的是url字符串,那么就直接返回这个字符串本身,如果传入的是URL对象,那么就返回这个对象toString()后的结果。 getCacheKey()方法中的逻辑直接就是将图片的url地址进行返回来作为缓存Key的。 那么其实我们只需要重写这个getCacheKey()方法,加入一些自己的逻辑判断,就能为glide图片加入我们自己的key。 首先构造的MyGlideKey,重写getCacheKey()方法,

class MyGlideKey(url: String?, private val eventId: String) : GlideUrl(url) { override fun getCacheKey(): String { return eventId } }

然后将MyGlideKey对象直接传入load,这样就可以在存入glide缓存图片的时候,添加我们自己定义的用于标识图片的key ,

val myGlideKey = MyGlideKey(url, imgId) //glide参数配置 val options = RequestOptions() //硬盘缓存设置(缓存原始图片) options.diskCacheStrategy(DiskCacheStrategy.DATA) Glide.with(this) .asBitmap() .load(myGlideKey) .apply(options) .into(pic_network_url)

glide的参数diskCacheStrategy是用来配置图片缓存的方式分为五种,通过调用diskCacheStrategy()方法传入不同的参数 1,DiskCacheStrategy.NONE// 表示不缓存任何内容 2,DiskCacheStrategy.DATA// 表示只缓存原始图片 3,DiskCacheStrategy.RESOURCE// 表示只缓存转换过后的图片 4,DiskCacheStrategy.ALL // 表示既缓存原始图片,也缓存转换过后的图片 5,DiskCacheStrategy.AUTOMATIC//表示让Glide根据图片资源智能地选择

这里要提一下在看glide源码的时候,还记得之前硬盘缓存中说的调用loadFromCache()从缓存当中读取数据有两种方法,如果是decodeResultFromCache()方法就直接将数据解码并返回,如果是decodeSourceFromCache()方法,还要调用一下transformEncodeAndTranscode()方法先将数据转换一下再解码并返回。这两个方法中在调用loadFromCache()方法时传入的参数是不一样的,一个传入的是resultKey,另外一个却又调用了resultKey的getOriginalKey()方法。

Glide的缓存Key是由10个参数共同组成的,包括图片的width、height等等。但如果我们是缓存的原始图片,不用对图片做任何的变化,其实并不需要这么多的参数,只需要id和signature这两个参数。 我们这里构造的MyGlideKey只使用了id和signature这两个参数来构成缓存Key,其实就是相当于是getOriginalKey()的方法。我们在使用自定义构造key的时候,要将硬盘缓存类型选择 DiskCacheStrategy.DATA,因为使用我们构造的key是属于OriginalKey是对应的原始图片,若我们设置缓存类型是转换后的图片,通过我们自定义的key,glide是找不到转换后的缓存的图片的。

2.使用唯一key获取缓存图片 1)显示缓存图片 val myGlideKey = MyGlideKey(url, imgId) Glide.with(this) .load(myGlideKey) .into(cache_network_url)

通过这种方式显示的图片会先去观察内存缓存是否有这张图片,若有则显示内存缓存中的图片,若没有则会去找硬盘的缓存中是否存在这张图片,若有则显示,没有则不显示。 若我们不仅是想要显示这张图片,而是想要获取这张图片的bitmap,就可以用以下的方式,其中myGlideKey是我们构造的,imgId就是图片的key。

try { mGlideMemoryCache = Glide.with(this) .asBitmap() .load(myGlideKey) .into(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) .get() } catch (e: java.lang.Exception) { e.printStackTrace() }

然后将此bitmap显示在view上

cache_network_url.setImageBitmap(mGlideMemoryCache)

注意:若我们去获取bitmap则会报加载失败的错误,所以我们利用glide去获取bitmap的时候要注意,代码不仅要在子线程中执行,且要加入try-catch。

通过代码可以验证到以下的结论: 1、我们可以通过清除硬盘缓存图片后再通过glide显示图片,发现图片是可以正常显示的,由此可以证明,glide获取图片在硬盘缓存未获取的情况下,会去加载内存缓存。 2、我们修改myGlideKey当中的url,再次加载图片,发现显示的还是之前显示的图片,说明图片在通过key可以找到缓存图片的情况下,就是通过key找到并加载显示的。 3、我们修改myGlideKey当中的imgId为不存在的key,加载的图片是url对应的图片,说明图片会先去找imgId对应是否有缓存的图片,找到就加载显示,没找到会去下载url对应的图片。 4、我们修改myGlideKey当中的imgId为不存在的key,加载的图片是url是无效的使用glide显示缓存图片是空白的。

以下是清除glide缓存的方法如下,注意要在子线程中执行: 1)清除所有的内存缓存 Glide.get(this).clearMemory(); 2)清除所有的磁盘缓存 Glide.get(this).clearDiskCache(); 3)清除单个缓存 Glide.with(this).clear(imageView);

2)获取缓存图片的文件

我们不仅想显示缓存图片,要想要获取缓存图片的文件怎么获得?来看下glide具体获取缓存图片的方法,主要看DiskLruCacheWrapper类有个get方法,这个方法就是获取硬盘缓存的具体方法,

@Override public File get(Key key) { String safeKey = safeKeyGenerator.getSafeKey(key); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Get: Obtained: " + safeKey + " for for Key: " + key); } File result = null; try { // It is possible that the there will be a put in between these two gets. If so that shouldn't // be a problem because we will always put the same value at the same key so our input streams // will still represent the same data. final DiskLruCache.Value value = getDiskCache().get(safeKey); if (value != null) { result = value.getFile(0); } } catch (IOException e) { if (Log.isLoggable(TAG, Log.WARN)) { Log.w(TAG, "Unable to get from disk cache", e); } } return result; }

仿照glide获取图片,我们也可以来写一个通过图片的key获取缓存图片的方法getCacheFileForUrl。我们要做的是,先获取glide缓存图片的key,然后通过这个key从硬盘缓存中取出图片。

fun getCacheFileForUrl(id: String?): File? { //DataCacheKey是glide缓存key的数据结构,可以从glide库中直接拷贝过来使用 val dataCacheKey = DataCacheKey(GlideUrl(id), EmptySignature.obtain()) //由于key可能含有特殊字符,需要使用SHA-256对key进行编码 val safeKeyGenerator = SafeKeyGenerator() Log.d("xq", "dataCacheKey=$dataCacheKey") val safeKey = safeKeyGenerator.getSafeKey(dataCacheKey) Log.d("xq", "safeKey=$safeKey") try { /** 获取缓存目录 通过open创建 DiskLruCache 1、磁盘缓存在文件系统中的路径 注意:externalCacheDir是外部缓存目录,如果是内部缓存应传入cacheDir 2、版本号 一般就设置1,版本号发生改变会清空之前缓存的文件 3、单个节点对应的数据数量,一般就设置 1 4、缓存总容量,超过容量DiskLruCache会清除一些缓存文件 */ val cacheSize = 1024 * 1024 * 50 //50MB val diskLruCache = DiskLruCache.open( File(externalCacheDir, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR), 1, 1, cacheSize.toLong() ) //value是DiskLruCache通过get方法获取的缓存文件输入流对象 val value = diskLruCache[safeKey] if (value != null) { //通过文件输入流获取文件,单个节点设置数据数量为1,所以这里获取index为0即可 return value.getFile(0) } } catch (e: IOException) { e.printStackTrace() } return null }

SafeKeyGenerator中的代码可以看到key的产生规则

/** * 产生位唯一安全的缓存key */ val messageDigest = MessageDigest.getInstance("SHA-256") key.updateDiskCacheKey(messageDigest) safeKey = Util.sha256BytesToHex(messageDigest.digest())

注意: 1、因为图片缓存地方可能不一样,缓存目录如果是在InternalCacheDiskCacheFactory,缓存图片File的目录应传入cacheDir,若是ExternalCacheDiskCacheFactory应传入externalCacheDir。 2、以上我们使用了DiskLruCache的创建,缓存的获取get()的方法,DiskLruCache还有添加edit(),移除remove(),删除delete()等方法用于磁盘缓存的操作。 打印出来可以看到safeKey就是对key进行编码后的结果: 在这里插入图片描述 通过以上分析来验证下缓存图片的获取和显示,首先显示两张图片,他们有相同的imgId

val myGlideKey = MyGlideKey(url2, imgId) val myGlideKey2 = MyGlideKey(url3, imgId) //网络图片加载,显示url3,自定义的imgId Glide.with(this) .asBitmap() .load(myGlideKey2) .apply(options) .into(pic_network_url3) //网络图片加载,显示url2,自定义的imgId Glide.with(this) .asBitmap() .load(myGlideKey) .apply(options) .into(pic_network_url2)

显示结果并点击获取缓存图片的路径,显示缓存获取图片以及图片的路径如下图所示: 在这里插入图片描述 可以观察到有着相同的imgId但是不同url的图片,在glide当中缓存的图片是一样的,这就解决了利用glide缓存图片,图片的url可能变化导致获取不到缓存图片的情况。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3